﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;  //串口调试用
using Modbus.Device;    //NModbus4库使用
using System.IO; //文件操作使用
using ExcelDataReader;
using System.Threading;
using System.Windows.Forms.DataVisualization.Charting; //绘制曲线
using Ymodem_tool; //ymodem升级

namespace FHtool
{
    public partial class stcForm : Form
    {
        #region 全局参数
        private SerialPort serialPort = new SerialPort();
        private byte mb_deviceID = 1;          //从机设备地址
        private IModbusSerialMaster mb_master; //Modbus主机

        private DataTable dtConfig; //配置数据表
        private DataTable dtParam;  //参数数据表
        private DataTable dtStatus; //状态数据表
        private DataTable dtAlarm;  //告警数据表
        private DataTable dtShow;   //快捷显示数据表

        private Thread thread_poll; //温控轮询线程
        private ManualResetEvent StcPollWaiter;// 温控轮询启动信号
        private int interval_poll = 1000;  //温控轮询间隔

        FileStream record_fs;       //记录文件流
        StreamWriter record_sw;
        #endregion

        public stcForm()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false; //屏蔽多线程调用同一控件时出现的异常

            //this.WindowState = FormWindowState.Maximized;//最大化
            this.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom; //窗体跟随
            this.FormBorderStyle = FormBorderStyle.None; //窗口无边框 None
            //this.MaximizeBox = false;//取消最大化按键
            //this.MinimizeBox = false;//取消最小化按键
            this.ControlBox = false;
        }

        #region 窗体加载
        private void stcForm_Load(object sender, EventArgs e)
        {
            // 端口名称列表框
            string[] ports = SerialPort.GetPortNames(); //检索当前的计算机的有效端口
            Array.Sort(ports);  //数组排序
            cbPortName.DropDownStyle = ComboBoxStyle.DropDownList;
            cbPortName.Items.AddRange(ports);//将数组添加到combox列表
            cbPortName.SelectedIndex = cbPortName.Items.Count > 0 ? 0 : -1;

            // 列出常用波特率,根据需求添加
            cbBaudRate.DropDownStyle = ComboBoxStyle.DropDownList;
            cbBaudRate.Items.Add("1200");
            cbBaudRate.Items.Add("2400");
            cbBaudRate.Items.Add("4800");
            cbBaudRate.Items.Add("9600");
            cbBaudRate.Items.Add("19200");
            cbBaudRate.Items.Add("38400");
            cbBaudRate.Items.Add("57600");
            cbBaudRate.Items.Add("115200");
            cbBaudRate.Items.Add("230400");
            cbBaudRate.SelectedIndex = 3;   //默认使用9600

            // 列出数据位
            cbDataBits.DropDownStyle = ComboBoxStyle.DropDownList;
            cbDataBits.Items.Add("8");
            cbDataBits.Items.Add("7");
            cbDataBits.Items.Add("6");
            cbDataBits.Items.Add("5");
            cbDataBits.SelectedIndex = 0;   //默认使用8数据位

            // 列出停止位
            cbStopBits.DropDownStyle = ComboBoxStyle.DropDownList;
            cbStopBits.Items.Add("0");
            cbStopBits.Items.Add("1");
            cbStopBits.Items.Add("1.5");
            cbStopBits.Items.Add("2");
            cbStopBits.SelectedIndex = 1;   //默认使用1个停止位

            // 列出奇偶校验位
            cbParity.DropDownStyle = ComboBoxStyle.DropDownList;
            cbParity.Items.Add("无");
            cbParity.Items.Add("奇校验");
            cbParity.Items.Add("偶校验");
            cbParity.SelectedIndex = 0;     //默认无校验

            tbxDeviceID.MaxLength = 3;

            btnSerialOpen.Text = serialPort.IsOpen ? "关闭串口" : "打开串口";
            pictureBox1.BackgroundImage = serialPort.IsOpen ? Properties.Resources.蓝点 : Properties.Resources.红点;



            // 调试日志文本框设置
            tbxLog.Multiline = true;  //指定文本框是否为多行文本框
            tbxLog.MaxLength = 32767; //在文本框中最多输入的文本的字符个数
            tbxLog.WordWrap = true;   //文本框中的文本自动换行
            tbxLog.ReadOnly = true;   //只读文本框
            tbxLog.ScrollBars = ScrollBars.Vertical; //垂直滚动条
            tbxLog.BackColor = Color.Black;
            tbxLog.ForeColor = Color.Green;
            tbxLog.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom;

            // modbus调试设置
            tbxRegAddr.MaxLength = 6;
            tbxRegNum.MaxLength = 3;
            cbFcode.Items.Add("03");//ReadHoldingRegisters
            cbFcode.Items.Add("04");//ReadInputRegisters
            cbFcode.Items.Add("06");//WriteSingleRegister
            cbFcode.Items.Add("16");//WriteMultipleRegisters
            cbFcode.Items.Add("01");//ReadCoils
            cbFcode.Items.Add("02");//ReadInputs
            cbFcode.Items.Add("05");//WriteSingleCoil
            cbFcode.Items.Add("15");//WriteMultipleCoils
            cbFcode.SelectedIndex = 0;

            btnDebugSend.Enabled = false;
            btnStcPoll.Enabled = false;
            btnStcSend.Enabled = false;
            btnStcRead.Enabled = false;

            // 配置文件加载
            dataGridViewParam.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom;
            dataGridViewStatus.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom;
            dataGridViewAlarm.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom;
            dataGridViewConfig.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom;

            dataGridViewParam.ReadOnly = true;
            dataGridViewStatus.ReadOnly = true;
            dataGridViewAlarm.ReadOnly = true;
            dataGridViewConfig.ReadOnly = false;
            

            // 加载默认工程文件excel
            var excelPath = Application.StartupPath + "\\stc_project.xlsx";
            LoadProjectExcel(excelPath);

            // 默认记录文件指定
            Record_FileSet(null);

            ChartStcInit();

            // 创建并启动温控轮询线程
            StcPollWaiter = new ManualResetEvent(false);
            thread_poll = new Thread(new ThreadStart(stcPollThreadMethod));
            thread_poll.IsBackground = true;
            thread_poll.Start();

            // 升级界面UI优化
            // label_UploadProgress.BackColor = Color.Transparent;
            // label_UploadProgress.Parent = progressBar_upgrade;
            // label_UploadProgress.Location = new Point(180, 13);
        }
        #endregion

        #region 日志输出
        private void log_output(string log)
        {
            if (tbxLog.TextLength > 30000)
                tbxLog.Text = "";

            tbxLog.AppendText(DateTime.Now.ToString("HH:mm:ss  ") + log + "\r\n");
        }

        // 日志双击清空
        private void tbxLog_DoubleClick(object sender, EventArgs e)
        {
            tbxLog.Text = "";
        }
        #endregion

        #region 设备ID输入限制（数字及范围）
        private void tbxDeviceID_KeyPress(object sender, KeyPressEventArgs e)
        {
            // IsNumber 判断输入按键是否位数字
            // (char)8 是退格键
            if (!(Char.IsNumber(e.KeyChar)) && (e.KeyChar != (char)8))
            {
                e.Handled = true; //判断为数字，可以输入
                log_output("请输入数字");
            }
        }

        #region 设备ID地址范围限制
        private void tbxDeviceID_TextChanged(object sender, EventArgs e)
        {
            if (tbxDeviceID.TextLength > 0)
            {
                if (Convert.ToUInt16(tbxDeviceID.Text) > 254 || Convert.ToUInt16(tbxDeviceID.Text) == 0)
                {
                    log_output("地址范围 1~254");
                    tbxDeviceID.Text = "";
                }
                else
                {
                    mb_deviceID = Convert.ToByte(tbxDeviceID.Text);
                }
            }
        }
        #endregion
        #endregion

        #region 设置串口的属性SetPortProperty
        private void SetPortProperty(ref SerialPort sp)//设置串口的属性
        {
            if (!sp.IsOpen)
            {
                sp.PortName = cbPortName.Text.Trim(); //设置串口名
            }
            sp.BaudRate = Convert.ToInt32(cbBaudRate.Text.Trim());//设置串口波特率
            sp.DataBits = Convert.ToInt16(cbDataBits.Text.Trim());//设置数据位

            float f = Convert.ToSingle(cbStopBits.Text.Trim());//设置停止位
            if (f == 0)
            {
                sp.StopBits = StopBits.None;
            }
            else if (f == 1.5)
            {
                sp.StopBits = StopBits.OnePointFive;
            }
            else if (f == 1)
            {
                sp.StopBits = StopBits.One;
            }
            else if (f == 2)
            {
                sp.StopBits = StopBits.Two;
            }
            else
            {
                sp.StopBits = StopBits.One;
            }

            string s = cbParity.Text.Trim();//设置奇偶校验位
            if (s.CompareTo("无") == 0)
            {
                sp.Parity = Parity.None;
            }
            else if (s.CompareTo("奇校验") == 0)
            {
                sp.Parity = Parity.Odd;
            }
            else if (s.CompareTo("偶校验") == 0)
            {
                sp.Parity = Parity.Even;
            }
            else
            {
                sp.Parity = Parity.None;
            }

        }
        #endregion

        #region 串口打开
        private void btnSerialOpen_Click(object sender, EventArgs e)
        {
            if (serialPort.IsOpen) //关闭串口
            {
                try
                {
                    serialPort.Close();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "串口关闭失败！", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
                mb_master.Dispose(); //释放mb_master
            }
            else //打开串口
            {
                SetPortProperty(ref serialPort);
                try
                {
                    serialPort.Open();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "串口无效或已被占用！", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }

                mb_master = ModbusSerialMaster.CreateRtu(serialPort);
                mb_master.Transport.ReadTimeout = 1000;
                mb_master.Transport.WriteTimeout = 1000;
                mb_master.Transport.Retries = 2;
            }

            if (serialPort.IsOpen) // 串口已经打开
            {
                btnSerialOpen.Text = "关闭串口";
                pictureBox1.BackgroundImage = Properties.Resources.蓝点;
                btnDebugSend.Enabled = true;
                btnStcPoll.Enabled = true;
                btnStcSend.Enabled = true;
                btnStcRead.Enabled = true;

                if (btnStcPoll.Text == "开始轮询")
                    btnStcPoll.PerformClick();
            }
            else
            {
                btnSerialOpen.Text = "打开串口";
                pictureBox1.BackgroundImage = Properties.Resources.红点;

                if (btnStcPoll.Text == "停止轮询")
                    btnStcPoll.PerformClick();

                btnDebugSend.Enabled = false;
                btnStcPoll.Enabled = false;
                btnStcSend.Enabled = false;
                btnStcRead.Enabled = false;
            }

            //btnSerialOpen.Text = serialPort.IsOpen ? "关闭串口" : "打开串口";
            //pictureBox1.BackgroundImage = serialPort.IsOpen ? Properties.Resources.蓝点 : Properties.Resources.红点;
        }
        #endregion

        #region Modbus调试
        #region 寄存器地址输入检查
        private void tbxRegAddr_KeyPress(object sender, KeyPressEventArgs e)
        {
            if ( !((e.KeyChar >= '0' && e.KeyChar <= '9')
                || (e.KeyChar >= 'a' && e.KeyChar <= 'f')
                || (e.KeyChar >= 'A' && e.KeyChar <= 'F')
                || (e.KeyChar == 'x')
                || (e.KeyChar == 'X')
                || (e.KeyChar == (char)8)) )
            {
                e.Handled = true;
                log_output("寄存器地址请输入16进制");
            }
        }
        #endregion

        #region 寄存器数量输入检查
        private void tbxRegNum_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!(Char.IsNumber(e.KeyChar)) && (e.KeyChar != (char)8))
            {
                e.Handled = true; //判断为数字，可以输入
                log_output("寄存器数量仅支持数字输入！");
            }
        }
        
        private void tbxRegNum_TextChanged(object sender, EventArgs e)
        {
            if (tbxRegNum.Text == "")
            {
                //tbxRegNum.Text = "1";
                return;
            }
            if (Convert.ToUInt16(tbxRegNum.Text) > 50 || Convert.ToUInt16(tbxRegNum.Text) == 0)
            {
                log_output("寄存器数量范围 1~50");
                tbxRegNum.Text = "1";
            }
        }
        #endregion

        #region 调试发送
        private void btnDebugSend_Click(object sender, EventArgs e)
        {
            ushort reg_addr = 1;
            ushort reg_num = 0;

            if (tbxRegNum.Text == "")
            {
                MessageBox.Show("请输入有效寄存器数量！");
                return;
            }
            else
            {
                reg_num = Convert.ToUInt16(tbxRegNum.Text);
            }

            if ((tbxRegAddr.Text.Substring(0, 2).Equals("0x")) || (tbxRegAddr.Text.Substring(0, 2).Equals("0X")))
            {
                string str_sub = tbxRegAddr.Text.Substring(2, tbxRegAddr.Text.Length - 2);
                reg_addr = Convert.ToUInt16(str_sub, 16);
            }
            else
            {
                reg_addr = Convert.ToUInt16(tbxRegAddr.Text);
            }

            switch (cbFcode.SelectedIndex)
            {
                case 0: //03 ReadHoldingRegisters
                    {
                        ushort[] reg_value = new ushort[reg_num];
                        try
                        {
                            reg_value = mb_master.ReadHoldingRegisters(mb_deviceID, reg_addr, reg_num);
                        }
                        catch (Exception ex)
                        {
                            log_output("ReadHoldingRegisters" + ex.Message);
                            return;
                        }

                        tbxRegResult.Text = "";
                        for (byte i=0; i<reg_num; i++)
                        {
                            tbxRegResult.AppendText(reg_value[i].ToString() + "  ");
                            log_output("Reg(" + Convert.ToString(reg_addr + i, 16) + ")=" + reg_value[i].ToString());
                        }
                    }
                    break;

                case 1: //04 ReadInputRegisters
                    {
                        ushort[] reg_value = new ushort[reg_num];
                        try
                        {
                            reg_value = mb_master.ReadInputRegisters(mb_deviceID, reg_addr, reg_num);
                        }
                        catch (Exception ex)
                        {
                            log_output("ReadInputRegisters" + ex.Message);
                            return;
                        }

                        for (byte i = 0; i < reg_num; i++)
                        {
                            log_output("Reg( " + Convert.ToString(reg_addr + i, 16) + ")=" + reg_value[i].ToString());
                        }
                    }
                    break;

                case 2: //06 WriteSingleRegister
                    {
                        log_output("正在开发中...");
                    }
                    break;

                case 3: //16 WriteMultipleRegisters
                    {
                        log_output("正在开发中...");
                    }
                    break;

                case 4: //01 ReadCoils
                    {
                        log_output("正在开发中...");
                    }
                    break;

                case 5: //02 ReadInputs
                    {
                        log_output("正在开发中...");
                    }
                    break;

                case 6: //05 WriteSingleCoil
                    {
                        log_output("正在开发中...");
                    }
                    break;

                case 7: //15 WriteMultipleCoils
                    {
                        log_output("正在开发中...");
                    }
                    break;

                default: break;
            }
        }
        #endregion
        #endregion


        #region 温控导入配置、载入工程、记录数据
        // 加载工程文件，生成DataGridView
        private void LoadProjectExcel(string excelPath)
        {
            try
            {
                FileStream stream = File.Open(excelPath, FileMode.Open, FileAccess.Read);
                IExcelDataReader reader = ExcelReaderFactory.CreateReader(stream);

                DataSet result = reader.AsDataSet(new ExcelDataSetConfiguration()
                {
                    UseColumnDataType = true,
                    ConfigureDataTable = (_) => new ExcelDataTableConfiguration() { UseHeaderRow = true }
                });

                DataTableCollection tableCollection = result.Tables;

                dtShow = tableCollection["SheetParam"].Clone();

                //dtParam = tableCollection["SheetParam"].DefaultView.ToTable(false, new string[] { "地址", "名称", "参数值", "单位" });
                dtParam = UpdateDataTable(tableCollection["SheetParam"]);
                DataColumn[] cols = new DataColumn[] { dtParam.Columns["地址"] };
                dtParam.PrimaryKey = cols; //设置主键
                dataGridViewParam.DataSource = dtParam;
                //dataGridViewParam.DataSource = dtParam.DefaultView.ToTable(false, new string[] { "地址", "名称", "参数值", "单位" });

                dtStatus = UpdateDataTable(tableCollection["SheetStatus"]);
                cols = new DataColumn[] { dtStatus.Columns["地址"] };
                dtStatus.PrimaryKey = cols; //设置主键
                dataGridViewStatus.DataSource = dtStatus;
                //dataGridViewStatus.DataSource = dtStatus.DefaultView.ToTable(false, new string[] { "地址", "名称", "参数值", "单位" });

                dtAlarm = UpdateDataTable(tableCollection["SheetAlarm"]);
                cols = new DataColumn[] { dtAlarm.Columns["地址"], dtAlarm.Columns["位"] };
                dtAlarm.PrimaryKey = cols; //设置主键
                dataGridViewAlarm.DataSource = dtAlarm;
                //dataGridViewAlarm.DataSource = dtAlarm.DefaultView.ToTable(false, new string[] { "地址", "位", "名称", "参数值" });

                dtConfig = UpdateDataTable(tableCollection["SheetConfig"]);
                cols = new DataColumn[] { dtConfig.Columns["地址"] };
                dtConfig.PrimaryKey = cols; //设置主键
                dataGridViewConfig.DataSource = dtConfig;
                //dataGridViewConfig.DataSource = dtConfig.DefaultView.ToTable(false, new string[] { "地址", "名称", "参数值", "单位" });
                dataGridViewConfig.Columns["地址"].ReadOnly = true;
                dataGridViewConfig.Columns["名称"].ReadOnly = true;
                dataGridViewConfig.Columns["参数值"].ReadOnly = false;
                dataGridViewConfig.Columns["单位"].ReadOnly = true;

                dataGridViewConfig.MultiSelect = true; //允许选择多项
                //dataGridViewConfig.SelectionMode = DataGridViewSelectionMode.FullRowSelect;


                dataGridViewShow.DataSource = dtShow.DefaultView.ToTable(false, new string[] { "名称", "参数值", "单位" });
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "工程导入文件错误！", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            for (int i = 0; i < dtConfig.Rows.Count; i++)
            {
                cbConfigName.Items.Add(dtConfig.Rows[i]["名称"].ToString());
            }
            cbConfigName.SelectedIndex = 0;
        }

        // 更改数据表地址列数据类型为 int，便于计算排序
        private DataTable UpdateDataTable(DataTable argDt)
        {
            DataTable dtResult = new DataTable();

            dtResult = argDt.Clone();    // 克隆表结构

            // 修改数据列类型
            foreach (DataColumn col in dtResult.Columns)
            {
                if (col.ColumnName == "地址")
                {
                    col.DataType = typeof(ushort); //指定地址列为Int类型,便于排序
                }
            }

            foreach (DataRow row in argDt.Rows)
            {
                DataRow rowNew = dtResult.NewRow();
                foreach (DataColumn col in dtResult.Columns)
                {
                    if (col.ColumnName == "地址")
                    {
                        rowNew[col.ColumnName] = Convert.ToUInt16(row[col.ColumnName].ToString(), 16);
                    }
                    else
                    {
                        rowNew[col.ColumnName] = row[col.ColumnName];
                    }
                    if (col.ColumnName == "是否启用")
                    {
                        if (row[col.ColumnName].ToString() != "NO")
                        {
                            dtResult.Rows.Add(rowNew);
                        }
                    }

                    if (col.ColumnName == "快捷显示")
                    {
                        if (row[col.ColumnName].ToString() == "YES")
                        {
                            dtShow.Rows.Add(rowNew.ItemArray);
                        }
                    }
                }
                //dtResult.Rows.Add(rowNew);
            }

            dtResult.DefaultView.Sort = "地址 ASC";

            return dtResult;
        }

        // 加载工程按键
        private void btnLoadExcel_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "Excel Workbook|*.xlsx|Excel 97-2003 Workbook|*.xls";
            openFileDialog.InitialDirectory = Application.StartupPath;
            openFileDialog.RestoreDirectory = true;
            openFileDialog.FilterIndex = 1;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                LoadProjectExcel(openFileDialog.FileName);
            }
        }

        // 导入配置按键
        private void btnLoadConfig_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "Excel Workbook|*.xlsx|Excel 97-2003 Workbook|*.xls";
            openFileDialog.InitialDirectory = Application.StartupPath;
            openFileDialog.RestoreDirectory = true;
            openFileDialog.FilterIndex = 1;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {

            }
        }

        // 记录文件设置
        private void Record_FileSet(string path)
        {
            if ( path == null || path == "" || !path.Contains(".csv") )
            {
                path = DateTime.Now.ToString("yyyy-MM-dd") + ".csv";
            }

            bool path_exist = File.Exists(path);

            record_fs = new FileStream(path, FileMode.Append, FileAccess.Write);
            record_sw = new StreamWriter(record_fs, System.Text.Encoding.UTF8);
            record_sw.AutoFlush = true;

            if (!path_exist)
            {
                string lineStr = "时间,";
                foreach (DataRow dr in dtParam.Rows)
                {
                    lineStr += dr["名称"] + ",";
                }
                foreach (DataRow dr in dtStatus.Rows)
                {
                    lineStr += dr["名称"] + ",";
                }
                record_sw.WriteLine(lineStr);
            }
        }

        // 记录数据
        private void Record_DtToCsv()
        {
            string lineStr = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + ",";

            foreach (DataRow dr in dtParam.Rows)
            {
                lineStr += dr["参数值"] + ",";
            }
            foreach (DataRow dr in dtStatus.Rows)
            {
                lineStr += dr["参数值"] + ",";
            }

            record_sw.WriteLine(lineStr);
        }

        // 记录数据按键（设置记录文件）
        private void btnStcRecord_Click(object sender, EventArgs e)
        {
            SaveFileDialog fd = new SaveFileDialog();
            fd.Title = "温控记录文件";
            fd.FileName = DateTime.Now.ToString("yyyy-MM-dd");
            fd.InitialDirectory = Application.StartupPath;
            fd.Filter = "csv文件 |*.csv";
            if (fd.ShowDialog() == DialogResult.OK)
            {
                Record_FileSet(fd.FileName);
            }
        }
        #endregion

        #region 温控参数输入值校验
        private void tbxConfigValue_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!(Char.IsNumber(e.KeyChar)) && (e.KeyChar != (char)8))
            {
                e.Handled = true; //判断为数字，可以输入
                log_output("参数值仅支持数字输入！");
            }
        }
        #endregion

        #region 温控参数设置、读取发送按钮
        private void btnStcSend_Click(object sender, EventArgs e)
        {
            ushort reg_value = 0;

            DataRow[] datarows = dtConfig.Select("名称= '" + cbConfigName.Text + "'");
            ushort reg_addr = Convert.ToUInt16(datarows[0]["地址"].ToString());

            if (tbxConfigValue.Text == "")
            {
                MessageBox.Show("请输入有效参数输入值！");
                return;
            }
            else
            {
                try { reg_value = Convert.ToUInt16(tbxConfigValue.Text); }
                catch { MessageBox.Show("参数输入值 is outside the range of the UInt16 type！"); return; }
            }

            try
            {
                mb_master.WriteSingleRegister(mb_deviceID, reg_addr, reg_value);
            }
            catch (Exception ex)
            {
                log_output("WriteSingleRegister" + ex.Message);
                return;
            }

            log_output("WriteReg(" + Convert.ToString(reg_addr, 10) + ")=" + reg_value.ToString());
        }

        private void btnStcRead_Click(object sender, EventArgs e)
        {
            ushort[] reg_value = new ushort[1];

            DataRow[] datarows = dtConfig.Select("名称= '" + cbConfigName.Text + "'");
            ushort reg_addr = Convert.ToUInt16(datarows[0]["地址"].ToString());

            try
            {
                reg_value = mb_master.ReadHoldingRegisters(mb_deviceID, reg_addr, 1);
            }
            catch (Exception ex)
            {
                log_output("WriteSingleRegister" + ex.Message);
                return;
            }

            tbxConfigValue.Text = reg_value[0].ToString();

            log_output("ReadReg(" + Convert.ToString(reg_addr, 10) + ")=" + reg_value[0].ToString());
        }
        #endregion

        #region 温控轮询
        #region 轮询间隔输入检查
        private void tbxInterval_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!(Char.IsNumber(e.KeyChar)) && (e.KeyChar != (char)8))
            {
                e.Handled = true; //判断为数字，可以输入
                log_output("参数值仅支持数字输入！");
            }
        }

        private void tbxInterval_TextChanged(object sender, EventArgs e)
        {
            if (tbxInterval.Text == "")
            {
                interval_poll = 1000;
                return;
            }

            interval_poll = Convert.ToInt32(tbxInterval.Text);
            if (interval_poll < 1000)
            {
                interval_poll = 1000;
            } 

            log_output("interval_poll = " + interval_poll.ToString());
        }
        #endregion

        #region 该轮询数据表方法遗弃，使用下面的方法
        private void MbPoll(ushort reg_addr, ushort reg_num, DataGridView dgv)
        {
            log_output("MbPoll reg_addr=" + reg_addr.ToString() + "  reg_num=" + reg_num.ToString());

            ushort[] reg_value = new ushort[reg_num];
            try
            {
                reg_value = mb_master.ReadHoldingRegisters(mb_deviceID, reg_addr, reg_num);
            }
            catch (Exception ex)
            {
                if (ex.Message.Length < 50)
                {
                    log_output("ReadHoldingRegisters" + ex.Message);
                }
                
                return;
            }

            for (byte i = 0; i < reg_num; i++)
            {
                for (int j = 0; j < dgv.Rows.Count-1; j++)
                {
                    if (dgv.Rows[j].Cells["地址"].Value.ToString() == (reg_addr + i).ToString())
                    {
                        if (dgv.Columns.Contains("位"))
                        {
                            string a = dgv.Rows[j].Cells["位"].Value.ToString();
                            byte bit_off = Convert.ToByte(dgv.Rows[j].Cells["位"].Value.ToString());

                            dgv.Rows[j].Cells["参数值"].Value = ((reg_value[i] >> bit_off)&0x01).ToString();
                        }
                        else
                        {
                            dgv.Rows[j].Cells["参数值"].Value = reg_value[i].ToString();
                            break; //找到直接写下一个寄存器
                        }
                    }
                }
            }
        }
        #endregion

        #region 温控轮询实现
        /// <summary>
        /// 读取从机寄存器并更新读取的值到数据表控件
        /// </summary>
        /// <param name="reg_addr">寄存器起始地址</param>
        /// <param name="reg_num">寄存器数量</param>
        /// <param name="dgv">更新DataTable</param>
        private void MbPoll(ushort reg_addr, ushort reg_num, DataTable dt)
        {
            log_output("MbPoll reg_addr=" + reg_addr.ToString() + "  reg_num=" + reg_num.ToString());

            ushort[] reg_value = new ushort[reg_num];
            try
            {
                reg_value = mb_master.ReadHoldingRegisters(mb_deviceID, reg_addr, reg_num);
            }
            catch (Exception ex)
            {
                if (ex.Message.Length < 50)
                {
                    log_output("ReadHoldingRegisters" + ex.Message);
                }

                return;
            }

            for (byte i = 0; i < reg_num; i++)
            {
                if (dt.Columns.Contains("位"))
                {
                    DataRow[] drs = dt.Select("地址='" + (reg_addr + i).ToString() + "'");
                    foreach (DataRow dr in drs)
                    {
                        byte bit_off = Convert.ToByte(dr["位"]);
                        int alram_value = (reg_value[i] >> bit_off) & 0x01;
                        int dr_index = dr.Table.Rows.IndexOf(dr);
                        if (alram_value == 0)
                        {
                            dr["参数值"] = "正常";
                            dataGridViewAlarm.Rows[dr_index].Cells["参数值"].Style.ForeColor = Color.Green;
                        }
                        else
                        {
                            dr["参数值"] = "告警";
                            dataGridViewAlarm.Rows[dr_index].Cells["参数值"].Style.ForeColor = Color.Red;
                        }
                    }
                }
                else
                {
                    DataRow[] drs = dt.Select("地址='" + (reg_addr + i).ToString() + "'");

                    //if (drs[0]["数据类型"] != DBNull.Value && drs[0]["数据类型"] != null)
                    //{
                    //    if (drs[0]["数据类型"].ToString() == "short")
                    //    {

                    //    }
                    //}

                    if (drs[0]["数据转换"] != DBNull.Value && drs[0]["数据转换"] != null)
                    {
                        drs[0]["参数值"] = ( (float)(reg_value[i] - ((reg_value[i] <= 32767) ? 0 : 65536)) / Convert.ToUInt16(drs[0]["数据转换"]) ).ToString("#0.0##");
                    }
                    else
                    {
                        drs[0]["参数值"] = (reg_value[i] - ((reg_value[i] <= 32767) ? 0 : 65536)).ToString();
                    }
                }
            }
        }

        private void DtPoll(DataTable dt)
        {
            // 地址列去重排序
            List<ushort> lstID = (from d in dt.AsEnumerable() select d.Field<ushort>("地址")).ToList();
            lstID = lstID.Distinct().ToList(); //去重
            lstID.Sort(); //排序

            ushort index_start = 0;
            ushort reg_num = 1;

            for (ushort i = 1; i< lstID.Count; i++)
            {
                if (lstID[i] != lstID[i - 1] + 1)
                {
                    reg_num = (ushort)(i - index_start);
                    MbPoll(lstID[index_start], reg_num, dt);
                    index_start = i;
                }
            }
            reg_num = (ushort)(lstID.Count - index_start);
            MbPoll(lstID[index_start], reg_num, dt);
        }

        private void stcPoll()
        {
            if (checkBoxParam.Checked) //参数轮询使能
            {
                DtPoll(dtParam);
            }

            if (checkBoxStatus.Checked)//状态轮询使能
            {
                DtPoll(dtStatus);
            }

            if (checkBoxAlarm.Checked)  //告警轮询使能
            {
                DtPoll(dtAlarm);
            }

            if (checkBoxRecord.Checked) //实时记录使能
            {
                Record_DtToCsv();
            }

            // 更新显示界面
            chartStcDraw(); //绘制温湿度曲线
            //label_temperature.Text = "当前温度 " + dtParam.Select("名称='回风温度485'")[0]["参数值"] + "℃";
            //label_humidity.Text = "当前湿度 " + dtParam.Select("名称='回风湿度485'")[0]["参数值"] + "%RH";
        }
        #endregion

        #region 温控轮询线程
        /// <summary>
        /// 温控轮询线程
        /// </summary>
        private void stcPollThreadMethod()
        {
            while (true)
            {
                try
                {
                    StcPollWaiter.WaitOne();
                    stcPoll();
                    Thread.Sleep(interval_poll);
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
        }
        #endregion

        #region 轮询按钮
        private void btnStcPoll_Click(object sender, EventArgs e)
        {
            if (btnStcPoll.Text == "开始轮询")
            {
                StcPollWaiter.Set();    // 温控启动轮询信号
                log_output("温控轮询启动");
            }
            else
            {
                StcPollWaiter.Reset();  // 温控停止轮询信号
                log_output("温控轮询停止");
            }

            btnStcPoll.Text = btnStcPoll.Text == "开始轮询" ? "停止轮询" : "开始轮询";
        }

        #endregion

        #endregion

        #region 配置输入校验
        private void dataGridViewConfig_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                if (e.RowIndex >= 0)
                {
                    dataGridViewConfig.ClearSelection(); //取消之前选中
                    dataGridViewConfig.Rows[e.RowIndex].Selected = true;
                    dataGridViewConfig.CurrentCell = dataGridViewConfig.Rows[e.RowIndex].Cells[e.ColumnIndex];
                    contextMenuStripConfig.Show(MousePosition.X, MousePosition.Y);
                }
            }
        }

        private void dataGridViewConfig_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
        {
            e.Control.KeyPress += new KeyPressEventHandler(dgvCertificate_KeyPress);
        }

        private void dgvCertificate_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!((e.KeyChar >= '0' && e.KeyChar <= '9')
                || (e.KeyChar == '.')
                || (e.KeyChar == '-')
                || (e.KeyChar == (char)8)))
            {
                e.Handled = true;
                log_output("配置请输入有效整数！");
            }
        }
        #endregion

        #region 配置表右键操作（设置、读取、全部读取、全部设置）
        #region 配置表数据全部下发
        private void DtSet(DataTable dt)
        {
            // 地址列去重排序
            List<ushort> lstID = (from d in dt.AsEnumerable() select d.Field<ushort>("地址")).ToList();
            lstID = lstID.Distinct().ToList(); //去重
            lstID.Sort(); //排序

            for (int i = lstID.Count - 1; i >= 0; i--)  //删除值为空的地址
            {
                DataRow[] drs = dt.Select("地址='" + (lstID[i]).ToString() + "'");
                if (drs[0]["参数值"] == DBNull.Value || drs[0]["参数值"] == null)
                {
                    lstID.RemoveAt(i);
                }
            }

            ushort index_start = 0;
            ushort reg_num = 1;
            ushort[] data;

            for (ushort i = 1; i < lstID.Count; i++)
            {
                if (lstID[i] != lstID[i - 1] + 1)
                {
                    reg_num = (ushort)(i - index_start);
                    data = new ushort[reg_num];

                    for (ushort j = 0; j < reg_num; j++)
                    {
                        DataRow[] drs = dt.Select("地址='" + (lstID[j + index_start]).ToString() + "'");

                        if (drs[0]["数据转换"] != DBNull.Value && drs[0]["数据转换"] != null)
                        {
                            data[j] = (ushort)(Convert.ToSingle(drs[0]["参数值"]) * Convert.ToUInt16(drs[0]["数据转换"]));
                        }
                        else
                        {
                            data[j] = Convert.ToUInt16(drs[0]["参数值"]);
                        }
                    }
                    mb_master.WriteMultipleRegisters(mb_deviceID, lstID[index_start], data);

                    index_start = i;
                }
            }

            reg_num = (ushort)(lstID.Count - index_start);
            data = new ushort[reg_num];
            for (ushort j = 0; j < reg_num; j++)
            {
                DataRow[] drs = dt.Select("地址='" + (lstID[j + index_start]).ToString() + "'");

                if (drs[0]["数据转换"] != DBNull.Value && drs[0]["数据转换"] != null)
                {
                    data[j] = (ushort)(Convert.ToSingle(drs[0]["参数值"]) * Convert.ToUInt16(drs[0]["数据转换"]));
                }
                else
                {
                    data[j] = Convert.ToUInt16(drs[0]["参数值"]);
                }
            }
            mb_master.WriteMultipleRegisters(mb_deviceID, lstID[index_start], data);
        }
        #endregion

        private void 设置ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (dataGridViewConfig.SelectedRows.Count <= 0)
            {
                MessageBox.Show("请选中后操作");
                return;
            }

            DialogResult dr = MessageBox.Show("确认设置吗？", "提示", MessageBoxButtons.OKCancel);
            if (dr == DialogResult.OK)
            {
                int k = dataGridViewConfig.CurrentRow.Index;
                if (dataGridViewConfig.Rows[k].Cells["参数值"].Value == DBNull.Value)
                {
                    MessageBox.Show("请输入有效值后操作");
                    return;
                }

                ushort reg_value = 0;
                float reg_value_temp = 0;
                ushort reg_addr = Convert.ToUInt16(dataGridViewConfig.Rows[k].Cells["地址"].Value.ToString());

                try { reg_value_temp = Convert.ToSingle(dataGridViewConfig.Rows[k].Cells["参数值"].Value.ToString()); }
                catch { MessageBox.Show("参数输入值 is not valid！"); return; }

                if (dataGridViewConfig.Rows[k].Cells["数据转换"].Value != DBNull.Value)
                {
                    reg_value = (ushort)(reg_value_temp * Convert.ToUInt16(dataGridViewConfig.Rows[k].Cells["数据转换"].Value.ToString()));
                }
                else
                {
                    reg_value = (ushort)reg_value_temp;
                }

                try
                {
                    mb_master.WriteSingleRegister(mb_deviceID, reg_addr, reg_value);
                }
                catch (Exception ex)
                {
                    log_output("WriteSingleRegister" + ex.Message);
                    return;
                }

                log_output(dataGridViewConfig.Rows[k].Cells["名称"].Value.ToString() + "  WriteReg(" + Convert.ToString(reg_addr, 10) + ")=" + reg_value.ToString());

            }
        }

        private void 读取ToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            if (dataGridViewConfig.SelectedRows.Count <= 0)
            {
                MessageBox.Show("请选中后操作");
                return;
            }

            int k = dataGridViewConfig.CurrentRow.Index;
            ushort reg_addr = Convert.ToUInt16(dataGridViewConfig.Rows[k].Cells["地址"].Value.ToString());

            try
            {
                ushort[] reg_value = mb_master.ReadHoldingRegisters(mb_deviceID, reg_addr, 1);
                DataRow[] drs = dtConfig.Select("地址='" + reg_addr.ToString() + "'");

                if (drs[0]["数据转换"] != DBNull.Value && drs[0]["数据转换"] != null)
                {
                    //drs[0]["参数值"] = ((float)reg_value[0] / Convert.ToUInt16(drs[0]["数据转换"])).ToString("#0.0##");
                    drs[0]["参数值"] = ((float)(reg_value[0] - ((reg_value[0] <= 32767) ? 0 : 65536)) / Convert.ToUInt16(drs[0]["数据转换"])).ToString("#0.0##");
                }
                else
                {
                    //drs[0]["参数值"] = reg_value[0].ToString();
                    drs[0]["参数值"] = (reg_value[0] - ((reg_value[0] <= 32767) ? 0 : 65536)).ToString();
                }
            }
            catch (Exception ex)
            {
                log_output("ReadHoldingRegisters" + ex.Message);
                return;
            }
        }

        private void 全部读取ToolStripMenuItem2_Click(object sender, EventArgs e)
        {
            DtPoll(dtConfig);
            log_output("全部读取结束！");
        }

        private void 全部设置ToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            DialogResult dr = MessageBox.Show("确认全部下发设置吗？", "提示", MessageBoxButtons.OKCancel);
            if (dr == DialogResult.OK)
            {
                DtSet(dtConfig);
                log_output("全部设置完成！");
            }
        }
        #endregion

        #region 绘制温湿度曲线
        private void ChartStcInit()
        {
            try
            {
                chartStc.BackColor = Color.Azure; //chartArea背景颜色

                // 曲线设置
                chartStc.Series["Rtemp"].MarkerColor = Color.Red; //点颜色
                chartStc.Series["Rtemp"].ChartType = SeriesChartType.Spline; //图表类型 设置为样条图曲线
                chartStc.Series["Rtemp"].MarkerSize = 5; //设置点标记的大小
                chartStc.Series["Rtemp"].Color = Color.Red; //设置曲线的颜色
                chartStc.Series["Rtemp"].BorderWidth = 2; //设置曲线宽度
                chartStc.Series["Rtemp"].IsValueShownAsLabel = false; //设置是否显示坐标标注
                chartStc.Series["Rtemp"].XValueType = ChartValueType.DateTime;

                chartStc.Series["Rhumid"].MarkerColor = Color.Green; //点颜色
                chartStc.Series["Rhumid"].ChartType = SeriesChartType.Spline; //图表类型 设置为样条图曲线
                chartStc.Series["Rhumid"].MarkerSize = 5; //设置点标记的大小
                chartStc.Series["Rhumid"].Color = Color.Green; //设置曲线的颜色
                chartStc.Series["Rhumid"].BorderWidth = 2; //设置曲线宽度
                chartStc.Series["Rhumid"].IsValueShownAsLabel = false; //设置是否显示坐标标注

                chartStc.Series["Ttemp"].MarkerColor = Color.Blue; //点颜色
                chartStc.Series["Ttemp"].ChartType = SeriesChartType.Spline; //图表类型 设置为样条图曲线
                chartStc.Series["Ttemp"].MarkerSize = 5; //设置点标记的大小
                chartStc.Series["Ttemp"].Color = Color.Blue; //设置曲线的颜色
                chartStc.Series["Ttemp"].BorderWidth = 2; //设置曲线宽度
                chartStc.Series["Ttemp"].IsValueShownAsLabel = false; //设置是否显示坐标标注


                // 设置滚动轴
                chartStc.ChartAreas[0].AxisX.ScrollBar.Enabled = true;
                chartStc.ChartAreas[0].AxisY.ScrollBar.Enabled = true;
                chartStc.ChartAreas[0].AxisX.ScrollBar.IsPositionedInside = false;//将滚动条放到图表外
                chartStc.ChartAreas[0].AxisY.ScrollBar.IsPositionedInside = false;//将滚动条放到图表外
                chartStc.ChartAreas[0].AxisX.ScrollBar.Size = 15;// 设置滚动条的大小
                chartStc.ChartAreas[0].AxisY.ScrollBar.Size = 15;
                chartStc.ChartAreas[0].AxisX.ScrollBar.ButtonStyle = ScrollBarButtonStyles.All;//滚动轴上的按钮全显示
                chartStc.ChartAreas[0].AxisX.ScrollBar.ButtonColor = Color.SkyBlue;
                chartStc.ChartAreas[0].AxisY.ScrollBar.ButtonStyle = ScrollBarButtonStyles.All;
                chartStc.ChartAreas[0].AxisY.ScrollBar.ButtonColor = Color.SkyBlue;

                // 设置X轴显示区域类型为秒
                chartStc.ChartAreas[0].AxisX.ScaleView.MinSizeType = DateTimeIntervalType.Seconds;
                chartStc.ChartAreas[0].AxisX.ScaleView.SizeType = DateTimeIntervalType.Seconds;
                chartStc.ChartAreas[0].AxisX.ScaleView.SmallScrollMinSizeType = DateTimeIntervalType.Seconds;
                chartStc.ChartAreas[0].AxisX.IntervalType = DateTimeIntervalType.Seconds;

                //chartStc.Legends.Clear();
                chartStc.ChartAreas[0].AxisX.Enabled = AxisEnabled.True;
                chartStc.ChartAreas[0].AxisX.LabelStyle.Enabled = true;
                chartStc.ChartAreas[0].AxisY.Enabled = AxisEnabled.True;
                chartStc.ChartAreas[0].AxisY.LabelStyle.Enabled = true;
                //设置游标
                chartStc.ChartAreas[0].CursorX.IsUserEnabled = true;
                chartStc.ChartAreas[0].CursorX.AutoScroll = true;
                chartStc.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
                chartStc.ChartAreas[0].CursorY.IsUserEnabled = true;
                chartStc.ChartAreas[0].CursorY.AutoScroll = true;
                chartStc.ChartAreas[0].CursorY.IsUserSelectionEnabled = true;
                //设置X轴是否可以缩放
                chartStc.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
                chartStc.ChartAreas[0].AxisY.ScaleView.Zoomable = true;

                // 设置自动放大与缩小的最小量
                chartStc.ChartAreas[0].AxisX.ScaleView.SmallScrollSize = double.NaN;
                chartStc.ChartAreas[0].AxisX.ScaleView.SmallScrollMinSize = 1;
                chartStc.ChartAreas[0].AxisY.ScaleView.SmallScrollSize = double.NaN;
                chartStc.ChartAreas[0].AxisY.ScaleView.SmallScrollMinSize = 1;
                //将X轴上格网取消
                chartStc.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
                //X轴、Y轴标题
                chartStc.ChartAreas[0].AxisX.Title = "";
                chartStc.ChartAreas[0].AxisY.Title = "";
                //设置X Y轴范围、刻度间隔
                chartStc.ChartAreas[0].AxisX.Minimum = DateTime.Now.ToOADate();
                chartStc.ChartAreas[0].AxisX.Maximum = DateTime.Now.AddSeconds(300).ToOADate();
                chartStc.ChartAreas[0].AxisX.Interval = 10;

                chartStc.ChartAreas[0].AxisY.Maximum = 100;
                chartStc.ChartAreas[0].AxisY.Minimum = -20;
                chartStc.ChartAreas[0].AxisY.Interval = 10;

                chartStc.ChartAreas[0].AxisX.LabelStyle.Format = "HH:mm:ss";//秒格式： hh:mm:ss.fff


                chartStc.GetToolTipText += new EventHandler<ToolTipEventArgs>(chart_GetToolTipText);


                //Thread threadChart = new Thread(new ThreadStart(chartTimerCall));
                //threadChart.IsBackground = true;
                //threadChart.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        // 温湿度曲线绘制
        private void chartStcDraw()
        {
            if (chartStc.Series[0].Points.Count() >= 300)
            {
                chartStc.Series[0].Points.RemoveAt(0);
                chartStc.ChartAreas[0].AxisX.Minimum = chartStc.Series[0].Points[0].XValue;
                chartStc.ChartAreas[0].AxisX.Maximum = DateTime.Now.AddSeconds(1).ToOADate();
            }

            double x = DateTime.Now.ToOADate();
            double y = 0;
            DataRow[] drs;

            if (cbxRtemp.Checked)
            {
                drs = dtParam.Select("名称='回风温度485'");
                if (drs != null && drs[0]["参数值"] != DBNull.Value && drs[0]["参数值"] != null)
                {
                    y = Convert.ToDouble(drs[0]["参数值"]);
                    chartStc.Series[0].Points.AddXY(x, y);
                }
            }
            if (cbxRhumid.Checked)
            {
                drs = dtParam.Select("名称='回风湿度485'");
                if (drs != null && drs[0]["参数值"] != DBNull.Value && drs[0]["参数值"] != null)
                {
                    y = Convert.ToDouble(drs[0]["参数值"]);
                    chartStc.Series[1].Points.AddXY(x, y);
                }
            }
            if (cbxTtemp.Checked)
            {
                drs = dtParam.Select("名称='送风温度NTC1'");
                if (drs != null && drs[0]["参数值"] != DBNull.Value && drs[0]["参数值"] != null)
                {
                    y = Convert.ToDouble(drs[0]["参数值"]);
                    chartStc.Series[2].Points.AddXY(x, y);
                }
            }
        }

        // 曲线鼠标坐标显示
        private void chart_GetToolTipText(object sender, ToolTipEventArgs e)
        {
            if (e.HitTestResult.ChartElementType == ChartElementType.DataPoint)
            {
                int i = e.HitTestResult.PointIndex;
                DataPoint dp = e.HitTestResult.Series.Points[i];
                e.Text = string.Format("{1:F3}", dp.XValue, dp.YValues[0]);
            }
        }

        // 曲线模拟定时测试
        private void chartTimerCall()
        {
            while(true)
            {
                if (chartStc.Series[0].Points.Count() >= 30)
                {
                    chartStc.Series[0].Points.RemoveAt(0);
                    //chartStc.ChartAreas[0].AxisX.Minimum = DateTime.Now.AddSeconds(-299).ToOADate();
                    chartStc.ChartAreas[0].AxisX.Minimum = chartStc.Series[0].Points[0].XValue;
                    chartStc.ChartAreas[0].AxisX.Maximum = DateTime.Now.AddSeconds(1).ToOADate();
                }

                Random ran2 = new Random();


                double x = DateTime.Now.ToOADate();
                double y = 0;

                if (cbxRtemp.Checked)
                {
                    y = Math.Round(ran2.NextDouble() * 30 + 50, 2);
                    chartStc.Series[0].Points.AddXY(x, y);
                }
                if (cbxRhumid.Checked)
                {
                    y = Math.Round(ran2.NextDouble() * 5 + 88, 2);
                    chartStc.Series[1].Points.AddXY(x, y);
                }
                if (cbxTtemp.Checked)
                {
                    y = Math.Round(ran2.NextDouble() * 10 + 65, 2);
                    chartStc.Series[2].Points.AddXY(x, y);
                }

                log_output("chartTimerCall " + x.ToString() + "   " + y.ToString());

                Thread.Sleep(1000);
            }
        }

        #endregion

        #region 软件升级
        private string _updateFilePath = ""; //升级文件路径
        private Thread _fileDownloadThread;  //升级下载线程
        private Ymodem ymodem = new Ymodem();
        private YModemType _yModemType = YModemType.YModem_1K; /* YModem_1K */

        #region 加载升级文件
        private void btn_openUpgradeFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "rbl升级文件|*.rbl";
            openFileDialog.InitialDirectory = Application.StartupPath;
            openFileDialog.RestoreDirectory = true;
            openFileDialog.FilterIndex = 1;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                tbx_upgradeFile.Text = openFileDialog.FileName;
                _updateFilePath = openFileDialog.FileName;
            }
        }
        #endregion

        #region 开始升级
        private void btn_startUpgrade_Click(object sender, EventArgs e)
        {
            if (_updateFilePath == "")
            {
                MessageBox.Show("请先加载升级文件！");
                return;
            }

            if (_fileDownloadThread != null && _fileDownloadThread.IsAlive)
            {
                ymodem.YmodemUploadFileStop();
                return;
            }

            if ((serialPort.IsOpen) && (btnSerialOpen.Text == "关闭串口"))
            {
                mb_master.Transport.WriteTimeout = 10;
                mb_master.Transport.Retries = 0;
                try { mb_master.WriteSingleRegister(mb_deviceID, 0x0020, 1); } 
                catch (Exception ex)
                {
                    log_output("WriteSingleRegister" + ex.Message);
                }

                /* TODO:进行升级指令下发 */
                btnSerialOpen.PerformClick();   /* 串口已打开并进行modbus通信 */
            }

            _fileDownloadThread = new Thread(() =>
            {
                FileDownloadProc();
            });
            _fileDownloadThread.IsBackground = true; //设置为后台进程,关闭当前窗体或线程运行结束就会结束该线程
            _fileDownloadThread.Start();
            this.btn_startUpgrade.Text = _fileDownloadThread.IsAlive ? "取消升级" : "开始升级";
        }
        #endregion

        #region 文件下载线程
        private bool FileDownloadProc()
        {
            //清空信息
            this.Invoke(new Action(() =>
            {
                progressBar_upgrade.Value = 0;
                this.label_UploadProgress.Text = "0 %";
            }));

            //串口占用检测
            try
            {
                this.Invoke(new Action(() =>
                {
                    serialPort.PortName = cbPortName.Text.Trim(); //设置串口名
                }));
                serialPort.Open();
                log_output("uart name=" + serialPort.PortName);
            }
            catch
            {
                log_output("打开设备的串口失败！");
                this.Invoke(new Action(() =>
                {
                    this.btn_startUpgrade.Text = "开始下载";
                }));
                return false;
            }
            serialPort.Close();

            //开始下载
            log_output("开始下载...");
            ymodem = new Ymodem();
            ymodem.path = _updateFilePath;
            ymodem.portName = cbPortName.Text.Trim();
            ymodem.baudRate = 115200;
            ymodem.NowDownloadProgressEvent += new EventHandler(NowDownloadProgressEvent);
            ymodem.DownloadResultEvent += new EventHandler(DownloadFinishEvent);
            ymodem.debug_output += new debugCallback(log_output);
            ymodem.YmodemUploadFile(_yModemType, 0);

            return true;
        }
        #endregion

        #region 下载进度委托及事件响应
        private void NowDownloadProgressEvent(object sender, EventArgs e)
        {
            int value = Convert.ToInt32(sender);
            this.Invoke(new Action(() =>
            {
                this.progressBar_upgrade.Value = value;
                this.label_UploadProgress.Text = value.ToString() + " %";
            }));
        }
        #endregion

        #region 下载完成委托及事件响应
        private void DownloadFinishEvent(object sender, EventArgs e)
        {
            bool finish = (Boolean)sender;
            this.Invoke(new Action(() =>
            {
                if (finish == true)
                {
                    log_output("下载成功！");
                    this.label_UploadProgress.Text = "下载成功！";
                }
                else
                {
                    log_output("下载失败！");
                    this.label_UploadProgress.Text = "下载失败！";
                }
                this.btn_startUpgrade.Text = "开始下载";
            }));
        }
        #endregion

        #endregion
    }
}
